using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Framework
{
	public static class ArrayUtil
	{
        #region Cast
        /// <summary>
        /// Casts the specified list to a new type, the list should be of type BaseType and the target should by of type ChildType. 
        /// A filter ensures that if there are other types in the base list then they are ignored.
        /// </summary>
        /// <typeparam name="TBaseType">The type of the base type.</typeparam>
        /// <typeparam name="TChildType">The type of the child type.</typeparam>
        /// <param name="list">The list.</param>
        /// <returns></returns>
        public static List<TChildType> Cast<TBaseType, TChildType>(List<TBaseType> list) where TChildType : class
        {
            var typedList = list.FindAll(product => product is TChildType);

            var result = new List<TChildType>();

            typedList.ForEach(product => result.Add(product as TChildType));

            return result;
        }
        #endregion

		#region InsertAt
		public static object[] InsertAt(object[] array, object obj, int index)
		{
			if (index < 0 || index >= array.Length + 1)
			{
				throw new ArgumentException("ArrayUtil.InsertAt - Index out of bounds");
			}

			object[] result = (object[])Array.CreateInstance(array.GetType().GetElementType(), array.Length + 1);

			if (index == 0)
			{  // InsertAt Index 0, Copy current array to position 1
				Array.Copy(array, 0, result, 1, array.Length);
			}
			else if (index == array.Length)
			{  // InsertAt End of Array, Leave current array as it is.
				Array.Copy(array, 0, result, 0, array.Length);
			}
			else
			{  // Need to split array in 2 and copy to both sides of index
				//            _.P( "Index : " + index );
				//            _.P( "Length: " + array.Length );
				//            _.P( "Copy from 0 to 0 for a length of: " + index );
				//            _.P( "Copy from " + index + " to " + (index+1 ) + " for a length of: " + (array.Length-index) );

				Array.Copy(array, 0, result, 0, index);
				Array.Copy(array, index, result, index + 1, array.Length - index);
			}

			result[index] = obj;

			return result;
		}
		#endregion

		#region FilterEmptyStrings
		public static List<string> FilterEmptyStrings(List<string> strings)
		{
			return FilterStrings(strings, ExcludeEmptyStrings);
		}

		public static string[] FilterEmptyStrings(params string[] strings)
		{
			return FilterStrings(strings, ExcludeEmptyStrings);
		}

		private static bool ExcludeEmptyStrings(string s)
		{
			return Is.NotEmptyString(s);
		}
		#endregion

		#region FilterStrings
		public static string[] FilterStrings(string[] strings, StringArrayFilterDelegate filter)
		{
			return FilterStrings(new List<string>(strings), filter).ToArray();
		}
		public static List<string> FilterStrings(List<string> strings, StringArrayFilterDelegate filter)
		{
		    return strings.Where(s => filter(s)).ToList();
		}
	    #endregion

		#region AddToArray
		/// <summary>
		/// Add an object to a fixed length array, returns a new array with the object added to the end.
		/// </summary>
		/// <param name="array">Array to add to</param>
		/// <param name="obj">Object to add</param>
		/// <returns>New enlarged array</returns>
		public static object[] AddToArray(object[] array, object obj)
		{
			object[] result = (object[])Array.CreateInstance(array.GetType().GetElementType(), array.Length + 1);

			Array.Copy(array, 0, result, 0, array.Length);

			result[result.Length - 1] = obj;

			return result;
		}

		/// <summary>
		/// Add an object to a fixed length array, returns a new array with the object added to the end.
		/// </summary>
		/// <param name="array"></param>
		/// <param name="arrayMore"></param>
		/// <returns></returns>
		public static object[] AddToArray(object[] array, object[] arrayMore)
		{
			if (array.GetType() != arrayMore.GetType())
			{
				throw new ArgumentException("Both arrays must be of the same type");
			}

			object[] result = (object[])Array.CreateInstance(array.GetType().GetElementType(), array.Length + arrayMore.Length);

			Array.Copy(array, 0, result, 0, array.Length);
			Array.Copy(arrayMore, 0, result, array.Length, arrayMore.Length);

			return result;
		}
		#endregion

		#region AddToStringArray
		/// <summary>
		/// Add a string to a fixed length string array, returns a new string array with the string added to the end.
		/// </summary>
		/// <param name="array">Array to add to</param>
		/// <param name="val">String to add</param>
		/// <returns>New enlarged string array</returns>
		public static string[] AddToStringArray(string[] array, string val)
		{
			var result = new string[array.Length + 1];

			Array.Copy(array, 0, result, 0, array.Length);

			result[result.Length - 1] = val;

			return result;
		}

		/// <summary>
		/// Add a string to a fixed length string array, returns a new string array with the string added to the end.
		/// </summary>
		/// <param name="array">Array to add to</param>
		/// <param name="val">String to add</param>
		/// <returns>New enlarged string array</returns>
		public static string[] AddToStringArray(string[] array, string[] arrayMore)
		{
			string[] result = (string[])Array.CreateInstance(typeof(string), array.Length + arrayMore.Length);

			Array.Copy(array, 0, result, 0, array.Length);
			Array.Copy(arrayMore, 0, result, array.Length, arrayMore.Length);

			return result;
		}
		#endregion

		#region RemoveFromArray
		public static object[] RemoveFromArray(object[] array, int index)
		{
			if (index < 0 || index >= array.Length)
			{
				throw new ArgumentException("ArrayUtil.RemoveToArray - Index out of bounds");
			}

			if (array.Length == 1)
			{
				return (object[])Array.CreateInstance(array.GetType().GetElementType(), 0);
			}

			object[] result = (object[])Array.CreateInstance(array.GetType().GetElementType(), array.Length - 1);

			int arrayPos = 0;

			for (int i = 0; i < result.Length; i++)
			{
				if (arrayPos == index)
				{
					arrayPos++;
				}

				result[i] = array[arrayPos++];
			}

			return result;
		}
		#endregion

		#region RemoveInActiveItems
		public static void RemoveInActiveItems<T>(List<T> list)
		{
			for (int i = list.Count-1; i >= 0; i--)
			{
				object o = list[i];

				bool isActive = (bool)ReflectUtil.GetPropertyOrFieldValue(o, "IsActive");

				if (!isActive)
				{
					list.RemoveAt(i);
				}
				//if (i == 3 || i == 1)
				//{
				//    list.RemoveAt(i);
				//}
			}
		}
		#endregion

		#region FillCharArray
		public static void FillCharArray(char[] array, char c, int beg, int length)
		{
			int end = beg + length;

			if (beg < 0 || beg > array.Length || end > array.Length || end <= beg)
			{
				throw new ArgumentOutOfRangeException();
			}

			while (beg < end)
			{
				array[beg++] = c;
			}
		}

		public static void FillCharArray(char[] array, char c)
		{
			FillCharArray(array, c, 0, array.Length);
		}

		public static void FillCharArray(char[] array, char[] data, int beg, int length)
		{
			int end = beg + length;

			if (beg < 0 || beg > array.Length || end > array.Length || end <= beg)
			{
				throw new ArgumentOutOfRangeException();
			}

			int dataPos = 0;

			while (beg < end)
			{
				array[beg++] = data[dataPos++];
			}
		}
		public static void FillCharArray(char[] array, char[] data, int beg)
		{
			FillCharArray(array, data, beg, data.Length);
		}
		#endregion

        #region Convert_IntToString
        /// <summary>
        /// Convert an array of int to an array of string.
        /// </summary>
        /// <param name="numbers">The numbers.</param>
        /// <returns></returns>
        public static string[] Convert_IntToString(int[] numbers)
        {
            var result = new string[numbers.Length];

            for (int i = 0; i < numbers.Length; i++)
            {
                result[i] = numbers[i].ToString();
            }

            return result;
        }

        /// <summary>
        /// Convert an List<int> a List<string>.
        /// </summary>
        /// <param name="numbers">The numbers.</param>
        /// <returns></returns>
        public static List<string> Convert_IntToString(List<int> numbers)
        {
            if (numbers == null)
            {
                return new List<string>();
            }
            var result = new List<string>(numbers.Count);

            numbers.ForEach(num => result.Add(num.ToString()));

            return result;
        }
        #endregion

	    #region Translate
        /// <summary>
        /// Translates IEnumerable TSource into IEnumerable TTarget
        /// </summary>
        /// <typeparam name="TSource">The type of the source.</typeparam>
        /// <typeparam name="TTarget">The type of the result</typeparam>
        /// <param name="sources">The sources.</param>
        /// <param name="transformer">The transformer action.</param>
        /// <returns></returns>
        public static IList<TTarget> Translate<TSource, TTarget>(IEnumerable<TSource> sources, Func<TSource, TTarget> transformer)
	    {
	        var result = new List<TTarget>();

            if (sources == null)
            {
                return result;
            }

	        foreach (var source in sources)
	        {
                result.Add(transformer(source));
	        }

	        return result;
	    }
	    #endregion

        // *********************************************************************************
        // Dictionary Types
        // *********************************************************************************

        #region AsDictionary
        /// <summary>
        /// Convert a list of strings into a dictionary.
        /// </summary>
        /// <param name="list">The list.</param>
        /// <returns></returns>
        public static Dictionary<string, string> AsDictionary(List<string> list)
        {
            return AsDictionary(list, FrameworkConstant.DEFAULT_COMPARER);
        }

        /// <summary>
        /// Convert a list of strings into a dictionary.
        /// </summary>
        /// <param name="list">The list.</param>
        /// <param name="comparer">The comparer.</param>
        /// <returns></returns>
        public static Dictionary<string, string> AsDictionary(List<string> list, IEqualityComparer<string> comparer)
        {
            var result = new Dictionary<string, string>(comparer);

            foreach (var item in list)
            {
                result.Add(item, item);
            }

            return result;
        }
        #endregion

        // *********************************************************************************
        // Hashset Types
        // *********************************************************************************

        #region AsHashSet
        /// <summary>
        /// Convert a list of strings into a HashSet.
        /// </summary>
        /// <param name="list">The list.</param>
        /// <returns></returns>
        public static HashSet<string> AsHashSet(List<string> list)
        {
            return AsHashSet(list, FrameworkConstant.DEFAULT_COMPARER);
        }

        /// <summary>
        /// Convert a list of strings into a HashSet.
        /// </summary>
        /// <param name="list">The list.</param>
        /// <param name="comparer">The comparer.</param>
        /// <returns></returns>
        public static HashSet<string> AsHashSet(List<string> list, IEqualityComparer<string> comparer)
        {
            var result = new HashSet<string>(comparer);

            foreach (var item in list)
            {
                result.Add(item);
            }

            return result;
        }
        #endregion

        public static List<T> ShiftCircular<T>(int offset, List<T> list)
        {
            var array = list.ToArray();

            ShiftCircular(offset, array);

            return new List<T>(array);
        }

	    private static void ShiftCircular<T>(int offset, T[] array)
        {
            if (offset == 0 || array.Length <= 1)
            {
                return;
            }

            offset = offset % array.Length;

            if (offset == 0)
            {
                return;
            }

            if (offset < 0)
            {
                offset = array.Length + offset;
            }

            Array.Reverse(array, 0, array.Length);
            Array.Reverse(array, 0, offset);
            Array.Reverse(array, offset, array.Length - offset);
        }
    }

	public delegate bool StringArrayFilterDelegate(string filterOnString);
}
